// ----------------------------------------------------------------------------
//
// Proxy class for communicating with the local database.
//
// ----------------------------------------------------------------------------
using System;
using System.Diagnostics;
using Microsoft.WindowsErrorReporting.Services.Data.API;
using System.Configuration;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Data;

namespace WERServices
{
    /// <summary>
    /// Proxy class for communicating with the local database.
    /// </summary>
    public class Proxy
    {
        #region Fields
        private EventLog eventLog = null;
        private IDataStore dataStore;
        #endregion Fields

        #region Constructors
        /// <summary>
        /// Constructor that creates the Proxy object.
        /// </summary>
        /// <param name="eventLog">EventLog object used to write to the event log.</param>
        /// <param name="dataStore">Interface to the data store.</param>
        public Proxy(EventLog eventLog, IDataStore dataStore)
        {
            this.eventLog = eventLog;
            this.dataStore = dataStore;
        }
        #endregion Constructors

        #region Properties
        /// <summary>
        /// Gets the connection string from app.Config.
        /// </summary>
        internal string ConnectionString
        {
            get
            {
                return ConfigurationManager.ConnectionStrings["LocalWERDataDB"].ConnectionString;
            }
        }
        #endregion Properties

        #region Methods
        /// <summary>
        /// Method to save the Product object to the local WER database's Products table.
        /// </summary>
        /// <param name="product">Product object to save.</param>
        internal void SaveProduct(Product product)
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@ProductID", "int", 0, product.ID, true);
            this.dataStore.AddParameter("@ProductName", "nvarchar", 50, product.Name, false);
            this.dataStore.AddParameter("@ProductVersion", "nvarchar", 50, product.Version, false);
            this.dataStore.AddParameter("@DateCreated", "smalldatetime", 0, product.DateCreatedLocal, false);
            this.dataStore.AddParameter("@DateModified", "smalldatetime", 0, product.DateModifiedLocal, false);
            this.dataStore.AddParameter("@TotalEvents", "int", 0, product.TotalEvents, false);

            //
            // save the product in the database.
            //
            this.dataStore.SaveData("saveProduct_sp", this.ConnectionString);
        }

        /// <summary>
        /// Method to save the ApplicationFile object to the local WER database's Files table.
        /// </summary>
        /// <param name="file">ApplicationFile object to save.</param>
        /// <param name="product">Product object used to pull the id of the product.</param>
        internal void SaveFile(ApplicationFile file, Product product)
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@ProductID", "int", 0, product.ID, true);
            this.dataStore.AddParameter("@FileID", "int", 0, file.ID, false);
            this.dataStore.AddParameter("@FileName", "nvarchar", 400, file.Name, false);
            this.dataStore.AddParameter("@FileVersion", "nvarchar", 400, file.Version, false);
            this.dataStore.AddParameter("@DateCreated", "smalldatetime", 0, file.DateCreatedLocal, false);
            this.dataStore.AddParameter("@DateModified", "smalldatetime", 0, file.DateModifiedLocal, false);

            //
            // save the file in the database.
            //
            this.dataStore.SaveData("saveFile_sp", this.ConnectionString);
        }

        /// <summary>
        /// Method to save the cab information to the database.
        /// </summary>
        /// <param name="cab">Cab object to save.</param>
        /// <param name="fileEvent">Event object related to the cab.</param>
        internal void SaveCab(Cab cab, Event fileEvent)
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@CabID", "int", 0, cab.ID, true);
            this.dataStore.AddParameter("@CabSizeInBytes", "bigint", 0, cab.SizeInBytes, false);
            this.dataStore.AddParameter(
                "@CabFileNameWithPath"
                , "varchar"
                , 2048
                , System.IO.Path.Combine(
                    ConfigurationManager.AppSettings["CabPath"]
                    , cab.FileName
                )
                , false
            );
            this.dataStore.AddParameter("@DateCreated", "smalldatetime", 0, cab.DateCreatedLocal, false);
            this.dataStore.AddParameter("@DateModified", "smalldatetime", 0, cab.DateModifiedLocal, false);
            this.dataStore.AddParameter("@EventID", "int", 0, fileEvent.ID, false);
            this.dataStore.AddParameter("@EventTypeName", "nvarchar", 50, fileEvent.EventTypeName, false);

            //
            // save the cab data in the database.
            //
            this.dataStore.SaveData("saveCab_sp", this.ConnectionString);
        }

        /// <summary>
        /// Method to save the Event data to the database.
        /// </summary>
        /// <param name="fileEvent">Event object to save.</param>
        /// <param name="file">ApplicationFile object associated with the event.</param>
        internal void SaveEvent(Event fileEvent, ApplicationFile file)
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@FileID", "int", 0, file.ID, true);
            this.dataStore.AddParameter("@EventID", "int", 0, fileEvent.ID, false);
            this.dataStore.AddParameter("@EventTypeName", "nvarchar", 50, fileEvent.EventTypeName, false);
            this.dataStore.AddParameter("@DateCreated", "smalldatetime", 0, fileEvent.DateCreatedLocal, false);
            this.dataStore.AddParameter("@DateModified", "smalldatetime", 0, fileEvent.DateModifiedLocal, false);
            this.dataStore.AddParameter("@TotalHits", "bigint", 0, fileEvent.TotalHits, false);
            foreach (Parameter parameter in fileEvent.Signature.Parameters)
            {
                switch (parameter.Name.ToLower())
                {
                    case "applicationname":
                        this.dataStore.AddParameter("@ApplicationName", "nvarchar", 400, parameter.Value, false);
                        break;
                    case "applicationversion":
                        this.dataStore.AddParameter("@ApplicationVersion", "nvarchar", 24, parameter.Value, false);
                        break;
                    case "applicationtimestamp":
                        if (IsDateTimeSqlCompatible(DateTime.Parse(parameter.Value)) == true)
                        {
                            this.dataStore.AddParameter("@ApplicationStampDate", "datetime", 0, DateTime.Parse(parameter.Value), false);
                        }
                        break;
                    case "modulename":
                        this.dataStore.AddParameter("@ModuleName", "nvarchar", 400, parameter.Value, false);
                        break;
                    case "moduleversion":
                        this.dataStore.AddParameter("@ModuleVersion", "nvarchar", 24, parameter.Value, false);
                        break;
                    case "moduletimestamp":
                        if (IsDateTimeSqlCompatible(DateTime.Parse(parameter.Value)) == true)
                        {
                            this.dataStore.AddParameter("@ModuleStampDate", "datetime", 0, DateTime.Parse(parameter.Value), false);
                        }
                        break;
                    case "offset":
                        this.dataStore.AddParameter("@Offset", "nvarchar", 50, parameter.Value, false);
                        break;
                    case "exceptioncode":
                        this.dataStore.AddParameter("@ExceptionCode", "char", 10, parameter.Value, false);
                        break;
                }
            }

            //
            // save the event in the database.
            //
            this.dataStore.SaveData("saveEvent_sp", this.ConnectionString);
        }

        /// <summary>
        /// Method to save the event hit data for the event to the database.
        /// </summary>
        /// <param name="fileEvent">Event object about which the event hit data is being saved.</param>
        /// <param name="eventInfo">EventInfo object containing the hit data about the event.</param>
        internal void SaveEventHit(Event fileEvent, EventInfo eventInfo)
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@EventID", "bigint", 0, fileEvent.ID, true);
            this.dataStore.AddParameter("@EventTypeName", "nvarchar", 50, fileEvent.EventTypeName, false);
            this.dataStore.AddParameter("@DateCreated", "smalldatetime", 0, eventInfo.DateCreatedLocal, false);
            this.dataStore.AddParameter("@DateModified", "smalldatetime", 0, eventInfo.DateModifiedLocal, false);
            this.dataStore.AddParameter("@TotalHits", "bigint", 0, eventInfo.TotalHits, false);
            this.dataStore.AddParameter("@OperatingSystemName", "nvarchar", 100, eventInfo.OperatingSystemName, false);
            this.dataStore.AddParameter("@OperatingSystemVersion", "varchar", 100, eventInfo.OperatingSystemVersion, false);
            this.dataStore.AddParameter("@LocaleID", "smallint", 0, eventInfo.LCID, false);
            this.dataStore.AddParameter("@LocaleCode", "varchar", 15, eventInfo.Locale, false);
            this.dataStore.AddParameter("@LocaleName", "nvarchar", 400, eventInfo.Language, false);

            //
            // save the event hit data in the database.
            //
            this.dataStore.SaveData("saveEventHit_sp", this.ConnectionString);
        }

        /// <summary>
        /// Internal method to commit the event hit data.
        /// </summary>
        internal void CommitEventHits()
        {
            //
            // add the parameters
            //
            this.dataStore.AddParameter("@Commit", "bit", 0, 0, true);

            //
            // commit the event hit data from the stage event hits table to the event hits table
            //
            this.dataStore.SaveData("commitEventHits_sp", this.ConnectionString);
        }

        /// <summary>
        /// Method to validate whether input date is between SQL Server's min and max dates.
        /// </summary>
        /// <param name="dateTimeToValidate"></param>
        /// <returns></returns>
        private bool IsDateTimeSqlCompatible(DateTime dateTimeToValidate)
        {
            DateTime sqlMinDate = new DateTime(1753, 1, 1);
            DateTime sqlMaxDate = new DateTime(9999, 12, 31);

            if (dateTimeToValidate >= sqlMinDate && dateTimeToValidate <= sqlMaxDate)
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Internal method to get the number of days to fetch event hit data for.
        /// </summary>
        /// <param name="fileEvent">Event object to get event hit data for.</param>
        internal int GetEventHitDays(Event fileEvent)
        {
            int numberOfDays = 0;

            //
            // add the parameters
            //
            this.dataStore.AddParameter("@EventID", "bigint", 0, fileEvent.ID, true);
            this.dataStore.AddParameter("@EventTypeName", "nvarchar", 50, fileEvent.EventTypeName, false);

            //
            // get the number of days from the dataset
            //
            DataSet dataSet = this.dataStore.ReadData("getEventHitDays_sp", this.ConnectionString);

            if (dataSet != null)
            {
                if (dataSet.Tables.Count > 0)
                {
                    DataTable daysDataTable = dataSet.Tables[0];
                    if (daysDataTable != null)
                    {
                        foreach (DataRow dataRow in daysDataTable.Rows)
                        {
                            int.TryParse(dataRow[0].ToString(), out numberOfDays);
                        }
                    }
                    else
                    {
                        throw new NullReferenceException("logDataTable is null.");
                    }

                }
                else
                {
                    throw new IndexOutOfRangeException("dataSet does not contain any DataTable objects.");
                }
            }
            else
            {
                throw new NullReferenceException("dataSet object is null.");
            }

            //
            // return the number of days to get hit data for
            //
            return numberOfDays;
        }

        /// <summary>
        /// Method to process the log data for the WER service data pull.
        /// </summary>
        /// <param name="maxFeedDate">Latest date for the feed. This is the date till which the WER Service has data till.</param>
        /// <returns>Returns a generic list of Log objects containing the dates to pull data for.</returns>
        internal List<Log> ProcessLogs(int fileID, DateTime maxFeedDate)
        {
            //
            // create a new generic list of Log structures.
            //
            List<Log> logList = new List<Log>();

            //
            // add the parameters.
            //
            this.dataStore.AddParameter("@FileID", "int", 0, fileID, true);
            this.dataStore.AddParameter("@MaxFeedDate", "smalldatetime", 0, maxFeedDate, false);

            //
            // call the read data method.
            //
            DataSet dataSet = this.dataStore.ReadData("processLogs_sp", this.ConnectionString);

            //
            // loop the sql data reader and populate the array.
            //
            if (dataSet != null)
            {
                if (dataSet.Tables.Count > 0)
                {
                    DataTable logDataTable = dataSet.Tables[0];
                    if (logDataTable != null)
                    {
                        foreach (DataRow dataRow in logDataTable.Rows)
                        {
                            logList.Add(
                                new Log(
                                    int.Parse(dataRow[0].ToString())
                                    , int.Parse(dataRow[1].ToString())
                                    , DateTime.Parse(dataRow[2].ToString())
                                    , DateTime.Parse(dataRow[3].ToString())
                                )
                            );
                        }
                    }
                    else
                    {
                        throw new NullReferenceException("logDataTable is null.");
                    }

                }
                else
                {
                    throw new IndexOutOfRangeException("dataSet does not contain any DataTable objects.");
                }
            }
            else
            {
                throw new NullReferenceException("dataSet object is null.");
            }

            //
            // return the log list.
            //
            return logList;
        }

        /// <summary>
        /// Method to stamp the processing start and end dates for the log id.
        /// </summary>
        /// <param name="logID">The log id. This will correspond to the range start and end dates that were processed.</param>
        /// <param name="processingStartDate">The processing start date.</param>
        /// <param name="processingEndDate">The processing end date.</param>
        internal void StampLog(int logID, DateTime processingStartDate, DateTime processingEndDate)
        {
            //
            // add the parameters.
            //
            this.dataStore.AddParameter("@LogID", "int", 0, logID, true);
            this.dataStore.AddParameter("@ProcessingStartDate", "smalldatetime", 0, processingStartDate, false);
            this.dataStore.AddParameter("@ProcessingEndDate", "smalldatetime", 0, processingEndDate, false);

            //
            // stamp the log.
            //
            this.dataStore.SaveData("stampLog_sp", this.ConnectionString);
        }
        #endregion Methods
    }
}